package com.mgy.im.standard;

import com.mgy.im.autoconfigure.NettyWebSocketProperties;
import com.mgy.im.constants.Constant;
import com.mgy.im.pojo.Session;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;


import java.io.InputStream;

import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * xxx
 *
 * @author maguoyong
 * @date 2020/6/14
 */
@Slf4j
class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private final NettyWebSocketProperties properties;
    private WebSocketHandler webSocketHandler;

    public HttpRequestHandler(NettyWebSocketProperties properties, WebSocketHandler webSocketHandler) {
        this.properties = properties;
        this.webSocketHandler = webSocketHandler;

    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        log.info("===========> {}, {}", properties.getPath(), request.uri());

        // Handle a bad request.
        if (!request.decoderResult().isSuccess()) {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
            sendHttpResponse(ctx, request, response);
            return;
        }

        // Allow only GET methods.
        if (request.method() != GET) {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
            sendHttpResponse(ctx, request, response);
            return;
        }

        HttpHeaders headers = request.headers();
        String host = headers.get(HttpHeaderNames.HOST);
        if (StringUtils.isEmpty(host)) {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
            sendHttpResponse(ctx, request, response);
            return;
        }

        if (!StringUtils.isEmpty(properties.getHost()) && !properties.getHost().equals("0.0.0.0") && !properties.getHost().equals(host.split(":")[0])) {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
            sendHttpResponse(ctx, request, response);
            return;
        }

        if (!request.headers().contains(UPGRADE) || !request.headers().contains(SEC_WEBSOCKET_KEY) || !request.headers().contains(SEC_WEBSOCKET_VERSION)) {
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN);
            sendHttpResponse(ctx, request, response);
            return;
        }

        if (HttpUtil.is100ContinueExpected(request)) {
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONFLICT);
            sendHttpResponse(ctx, request, response);
            return;
        }

        String uri = StringUtils.substringBefore(request.uri(), "?");
        if (!properties.getPath().equalsIgnoreCase(uri)) {
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, NOT_FOUND);
            sendHttpResponse(ctx, request, response);
            return;
        }
        // 握手
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(properties.getPath(), null, true, properties.getMaxFramePayloadLength());
        WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            ChannelPipeline pipeline = ctx.pipeline();
            pipeline.remove(ctx.name());

            handshaker.handshake(ctx.channel(), request).addListener(f -> {
                if (f.isSuccess()) {
                    Session session = new Session(ctx.channel());
                    ctx.channel().attr(Constant.SESSION_KEY).set(session);
                    webSocketHandler.afterConnectionEstablished(session);
                } else {
                    handshaker.close(ctx.channel(), new CloseWebSocketFrame());
                }
            });
        }


    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("出错：", cause);
        ctx.close();
        Session session = ctx.channel().attr(Constant.SESSION_KEY).get();
        webSocketHandler.handleError(session, cause);

    }


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        try {
            Session session = ctx.channel().attr(Constant.SESSION_KEY).get();
            webSocketHandler.afterConnectionClosed(session, "");
        } finally {

        }
        super.channelInactive(ctx);
    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
        int statusCode = response.status().code();
        if (statusCode != OK.code() && response.content().readableBytes() == 0) {
            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
        }
        HttpUtil.setContentLength(response, response.content().readableBytes());
        ChannelFuture f = ctx.channel().writeAndFlush(response);
        if (!HttpUtil.isKeepAlive(request) || statusCode != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }


    private static ByteBuf buildStaticRes(String resPath) {
        try {
            InputStream inputStream = HttpRequestHandler.class.getResourceAsStream(resPath);
            if (inputStream != null) {
                int available = inputStream.available();
                if (available != 0) {
                    byte[] bytes = new byte[available];
                    inputStream.read(bytes);
                    return ByteBufAllocator.DEFAULT.buffer(bytes.length).writeBytes(bytes);
                }
            }
        } catch (Exception e) {
        }
        return null;
    }


}